/**
 * @author Jeran Fox
 *
 * 2010-11-26
 */
package geometry;

import geometry.TriangleMesh.TriangleIndex;

import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL2;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import collision.BoundingBox;
import collision.BoundingSphere;

public class Sphere extends Drawable
{
    public static final Color3f DEFAULT_SPHERE_COLOR = new Color3f(0.1f, 0.1f, 0.7f);

    private double _radius;
    private Vector3d _origin;
    private BoundingBox _boundingBox;
    private BoundingSphere _boundingSphere;
    private TriangleMesh _mesh;

    public Sphere(double radius_)
    {
        this(radius_, new Vector3d(0, 0, 0), 2, DEFAULT_SPHERE_COLOR);
    }
    
    public Sphere(double radius_, Color3f color_)
    {
        this(radius_, new Vector3d(0, 0, 0), 2, color_);
    }

    public Sphere(double radius_, Vector3d origin_, int numSubDivisions, Color3f color_)
    {
    	_color.set(color_);
    	
        _origin = (Vector3d) origin_.clone();

        _radius = radius_;

        Point3d min = new Point3d(-_radius, -_radius, -_radius);
        Point3d max = new Point3d(_radius, _radius, _radius);

        min.add(_origin);
        max.add(_origin);

        _boundingBox = new BoundingBox(this, min, max);

        _boundingSphere = new BoundingSphere(this, new Point3d(_origin.x,
                _origin.y, _origin.z), _radius);

        initTriangleMesh(numSubDivisions);
    }

    // reference http://www.gamedev.net/reference/articles/article427.asp
    // num triangles = 8 * 4^(n)
    private void initTriangleMesh(int n)
    {
        List<Point3d> vertices = new ArrayList<Point3d>();
        List<TriangleIndex> triangles = new ArrayList<TriangleIndex>();
        
        // create initial octahedron
        vertices.add(new Point3d(_radius, 0, 0));
        vertices.add(new Point3d(-_radius, 0, 0));
        vertices.add(new Point3d(0, _radius, 0));
        vertices.add(new Point3d(0, -_radius, 0));
        vertices.add(new Point3d(0, 0, _radius));
        vertices.add(new Point3d(0, 0, -_radius));

        triangles.add(new TriangleIndex(0, 2, 4));
        triangles.add(new TriangleIndex(2, 1, 4));
        triangles.add(new TriangleIndex(1, 3, 4));
        triangles.add(new TriangleIndex(3, 0, 4));
        triangles.add(new TriangleIndex(0, 5, 2));
        triangles.add(new TriangleIndex(2, 5, 1));
        triangles.add(new TriangleIndex(1, 5, 3));
        triangles.add(new TriangleIndex(3, 5, 0));

        // subdivide n times
        for (int i = 0; i < n; i++)
        {
            int terminationSize = triangles.size();
            for (int j = 0; j < terminationSize; j++)
            {
                TriangleIndex ti = triangles.get(j);

                // create new vertices at midpoints of current triangle's edges,
                // then normalize and scale to radius
                Vector3d midab = new Vector3d();
                midab.add(vertices.get(ti._a),
                        vertices.get(ti._b));
                midab.normalize();
                midab.scale(_radius);
                Vector3d midac = new Vector3d();
                midac.add(vertices.get(ti._a),
                        vertices.get(ti._c));
                midac.normalize();
                midac.scale(_radius);
                Vector3d midbc = new Vector3d();
                midbc.add(vertices.get(ti._b),
                        vertices.get(ti._c));
                midbc.normalize();
                midbc.scale(_radius);

                // get indexes of new vertices and add new vertices
                int midabIndex = vertices.size();
                Point3d p;
                p = new Point3d();
                p.set(midab);
                vertices.add(p);
                int midacIndex = vertices.size();
                p = new Point3d();
                p.set(midac);
                vertices.add(p);
                int midbcIndex = vertices.size();
                p = new Point3d();
                p.set(midbc);
                vertices.add(p);

                // add three new triangles, and update old one
                triangles.add(new TriangleIndex(midabIndex, ti._b,
                        midbcIndex));
                triangles.add(new TriangleIndex(midacIndex, midbcIndex,
                        ti._c));
                triangles.add(new TriangleIndex(midabIndex, midbcIndex,
                        midacIndex));
                ti._b = midabIndex;
                ti._c = midacIndex;
            }
        }
        
        _mesh = new TriangleMesh(vertices, triangles, _color);
    }

    @Override
    public void build(GL2 gl)
    {
        _listNum = gl.glGenLists(1);
        gl.glNewList(_listNum, GL2.GL_COMPILE);
        // gl.glPushMatrix();
        // gl.glScaled(_radius, _radius, _radius);
        for (Triangle t : _mesh._triangles)
            t.draw(gl);
        // gl.glPopMatrix();

        gl.glEndList();
    }

    public BoundingBox getBoundingBox()
    {
        return _boundingBox;
    }

    public BoundingSphere getBoundingSphere()
    {
        return _boundingSphere;
    }

    @Override
    public TriangleMesh getTriangleMesh()
    {
        return _mesh;
    }
}
